use async_trait::async_trait;
use derive_more::Debug;
use napi::{Status, bindgen_prelude::External, threadsafe_function::ThreadsafeFunction};
use rspack_collections::IdentifierSet;
use rspack_core::{
  ApplyContext, CompilationRevokedModules, CompilerId, CompilerOptions, ModuleIdentifier, Plugin,
};
use rspack_error::ToStringResultToRspackResultExt;
use rspack_hook::{plugin, plugin_hook};

pub type CleanupRevokedModulesTsFn = ThreadsafeFunction<
  External<(CompilerId, Vec<ModuleIdentifier>)>,
  (),
  External<(CompilerId, Vec<ModuleIdentifier>)>,
  Status,
  false,
  true,
  1,
>;

#[plugin]
#[derive(Debug)]
pub struct JsCleanupPlugin {
  #[debug(skip)]
  cleanup_revoked_modules: CleanupRevokedModulesTsFn,
}

impl JsCleanupPlugin {
  pub fn new(cleanup_revoked_modules: CleanupRevokedModulesTsFn) -> Self {
    Self::new_inner(cleanup_revoked_modules)
  }
}

impl Plugin for JsCleanupPlugin {
  fn name(&self) -> &'static str {
    "rspack.JsCleanupPlugin"
  }

  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> rspack_error::Result<()> {
    ctx
      .compilation_hooks
      .revoked_modules
      .tap(revoked_modules::new(self));

    Ok(())
  }
}

#[plugin_hook(CompilationRevokedModules for JsCleanupPlugin)]
async fn revoked_modules(
  &self,
  compilation: &rspack_core::Compilation,
  revoked_modules: &IdentifierSet,
) -> rspack_error::Result<()> {
  if !revoked_modules.is_empty() {
    self
      .cleanup_revoked_modules
      .call_async(External::new((
        compilation.compiler_id(),
        revoked_modules.iter().cloned().collect::<Vec<_>>(),
      )))
      .await
      .to_rspack_result()?;
  }
  Ok(())
}
